diff --git a/Assets/BuiltIn/Shader/Compute/HLSL/IrradianceSH.hlsl b/Assets/BuiltIn/Shader/Compute/HLSL/IrradianceSH.hlsl new file mode 100644 index 000000000..5acbdcf9f --- /dev/null +++ b/Assets/BuiltIn/Shader/Compute/HLSL/IrradianceSH.hlsl @@ -0,0 +1,177 @@ +// Number of thread +#define MAX_NUM_THREAD 128 + +// MAX_NUM_THREAD * 6 faces +#define TANGENT_COUNT 768 + +// NUM FACE +#define NUM_FACE 6 + +// Size each group thread +#define RT_SIZE 16 + +// Uniform Constants +cbuffer cbConstants +{ + float4x4 uToTangentSpace[TANGENT_COUNT]; + float2 uPixelOffset; + float2 uFaceSize; +} + +// uRadianceMap texture +Texture2D uRadianceMap : register(t0); + +// Output result +RWBuffer OutputBuffer : register(u0); + +// Share group thread data +groupshared float3 ResultSH[RT_SIZE * RT_SIZE][9]; + +// SH compute function +void ProjectOntoSH(in float3 n, in float3 color, out float3 sh[9]) +{ + // Band 0 + sh[0] = 0.282095f * color; + + // Band 1 + sh[1] = 0.488603f * n.y * color; + sh[2] = 0.488603f * n.z * color; + sh[3] = 0.488603f * n.x * color; + + // Band 2 + sh[4] = 1.092548f * n.x * n.y * color; + sh[5] = 1.092548f * n.y * n.z * color; + sh[6] = 0.315392f * (3.0f * n.z * n.z - 1.0f) * color; + sh[7] = 1.092548f * n.x * n.z * color; + sh[8] = 0.546274f * (n.x * n.x - n.y * n.y) * color; +} + +// Params: +// groupID.x [0 -> MAX_NUM_THREAD] +// groupID.y [0 -> NUM_FACE] + +// groupThreadID.x [0 -> RT_SIZE] +// groupThreadID.y [0 -> RT_SIZE] + +// https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-attributes-numthreads +[numthreads(RT_SIZE, RT_SIZE, 1)] +void main( + uint3 groupID : SV_GroupID, + uint3 groupThreadID : SV_GroupThreadID, + uint3 dispatchThreadID : SV_DispatchThreadID, + uint groupIndex : SV_GroupIndex) +{ + // begin run thread + uint threadID = groupID.x; + uint faceID = groupID.y; + + uint pixelX = groupThreadID.x; + uint pixelY = groupThreadID.y; + + // need init variable + if (uPixelOffset.x == 0 && uPixelOffset.y == 0 && pixelX == 0 && pixelY == 0) + { + uint result = 0; + + // Calc offset id + uint id = (threadID * NUM_FACE + faceID) * 9; + + float4 zero = float4(0.0, 0.0, 0.0f, 0.0); + + // Write result + OutputBuffer[id + 0] = zero; + OutputBuffer[id + 1] = zero; + OutputBuffer[id + 2] = zero; + OutputBuffer[id + 3] = zero; + OutputBuffer[id + 4] = zero; + OutputBuffer[id + 5] = zero; + OutputBuffer[id + 6] = zero; + OutputBuffer[id + 7] = zero; + OutputBuffer[id + 8] = zero; + } + + GroupMemoryBarrierWithGroupSync(); + + const int3 pixelLocation = int3(pixelX + (int)uPixelOffset.x, pixelY + (int)uPixelOffset.y, 0.0); + + const int3 location = int3( + pixelLocation.x + faceID * uFaceSize.x, + pixelLocation.y + threadID * uFaceSize.y, + 0.0); + + // Gather RGB from the texels + float3 radiance = uRadianceMap.Load(location).xyz; + + // Calculate the location in [-1, 1] texture space + float u = (pixelLocation.x / float(uFaceSize.x)) * 2.0f - 1.0f; + float v = -((pixelLocation.y / float(uFaceSize.y)) * 2.0f - 1.0f); + + // Calculate weight + float temp = 1.0f + u * u + v * v; + float weight = 4.0f / (sqrt(temp) * temp); + radiance *= weight; + + // Extract direction from texel u,v + float3 dirVS = normalize(float3(u, v, 1.0f)); + float3 dirTS = mul(dirVS, (float3x3)uToTangentSpace[threadID * NUM_FACE + faceID]); + + // Project onto SH + float3 sh[9]; + ProjectOntoSH(dirTS, radiance, sh); + + // SH add + uint pixel = pixelY * RT_SIZE + pixelX; + + ResultSH[pixel][0] = sh[0]; + ResultSH[pixel][1] = sh[1]; + ResultSH[pixel][2] = sh[2]; + ResultSH[pixel][3] = sh[3]; + ResultSH[pixel][4] = sh[4]; + ResultSH[pixel][5] = sh[5]; + ResultSH[pixel][6] = sh[6]; + ResultSH[pixel][7] = sh[7]; + ResultSH[pixel][8] = sh[8]; + + GroupMemoryBarrierWithGroupSync(); + + // Sum total SH[RT_SIZE * RT_SIZE] by GPU MT store at [0] + uint totalSize = RT_SIZE * RT_SIZE; + + for(uint s = totalSize / 2; s > 0; s >>= 1) + { + if (pixel < s) + { + ResultSH[pixel][0] += ResultSH[pixel + s][0]; + ResultSH[pixel][1] += ResultSH[pixel + s][1]; + ResultSH[pixel][2] += ResultSH[pixel + s][2]; + ResultSH[pixel][3] += ResultSH[pixel + s][3]; + ResultSH[pixel][4] += ResultSH[pixel + s][4]; + ResultSH[pixel][5] += ResultSH[pixel + s][5]; + ResultSH[pixel][6] += ResultSH[pixel + s][6]; + ResultSH[pixel][7] += ResultSH[pixel + s][7]; + ResultSH[pixel][8] += ResultSH[pixel + s][8]; + } + + GroupMemoryBarrierWithGroupSync(); + } + + // Write result on first group thread + if (pixel == 0) + { + uint result = 0; + + // Calc offset id + uint id = (threadID * NUM_FACE + faceID) * 9; + + // Write result + OutputBuffer[id + 0] += float4(ResultSH[0][0], 0.0); + OutputBuffer[id + 1] += float4(ResultSH[0][1], 0.0); + OutputBuffer[id + 2] += float4(ResultSH[0][2], 0.0); + OutputBuffer[id + 3] += float4(ResultSH[0][3], 0.0); + OutputBuffer[id + 4] += float4(ResultSH[0][4], 0.0); + OutputBuffer[id + 5] += float4(ResultSH[0][5], 0.0); + OutputBuffer[id + 6] += float4(ResultSH[0][6], 0.0); + OutputBuffer[id + 7] += float4(ResultSH[0][7], 0.0); + OutputBuffer[id + 8] += float4(ResultSH[0][8], 0.0); + } +} \ No newline at end of file diff --git a/Assets/Sponza/Sponza.smesh b/Assets/Sponza/Sponza.smesh index 880c6d300..d3f1bc428 100644 --- a/Assets/Sponza/Sponza.smesh +++ b/Assets/Sponza/Sponza.smesh @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ffdc48c870de40cffde2374f986935235c237916b0f555bf457ab9e77f332611 +oid sha256:1f90b911eafdf819b6fab4931a2976ccdfabe8d5c5d6d1a3494c87198848c6fc size 71133750 diff --git a/Assets/Sponza/mesh_charts00.png b/Assets/Sponza/mesh_charts00.png deleted file mode 100644 index 589306b07..000000000 Binary files a/Assets/Sponza/mesh_charts00.png and /dev/null differ diff --git a/Assets/Sponza/mesh_charts01.png b/Assets/Sponza/mesh_charts01.png deleted file mode 100644 index 71b20ca30..000000000 Binary files a/Assets/Sponza/mesh_charts01.png and /dev/null differ diff --git a/Assets/Sponza/mesh_charts02.png b/Assets/Sponza/mesh_charts02.png deleted file mode 100644 index 90498252c..000000000 Binary files a/Assets/Sponza/mesh_charts02.png and /dev/null differ diff --git a/Assets/Sponza/mesh_charts03.png b/Assets/Sponza/mesh_charts03.png deleted file mode 100644 index d3e12795a..000000000 Binary files a/Assets/Sponza/mesh_charts03.png and /dev/null differ diff --git a/Assets/Sponza/mesh_charts04.png b/Assets/Sponza/mesh_charts04.png deleted file mode 100644 index 85e96cac2..000000000 Binary files a/Assets/Sponza/mesh_charts04.png and /dev/null differ diff --git a/Projects/Irrlicht/Include/IGPUCompute.h b/Projects/Irrlicht/Include/IGPUCompute.h new file mode 100644 index 000000000..2f6d9dfc7 --- /dev/null +++ b/Projects/Irrlicht/Include/IGPUCompute.h @@ -0,0 +1,49 @@ +// Copyright (C) 2020 Pham Hong Duc +// This file is part of the "Skylicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h +// Add irrlicht compute shader feature + +#ifndef __I_GPU_COMPUTE_H_INCLUDED__ +#define __I_GPU_COMPUTE_H_INCLUDED__ + +#include "IrrCompileConfig.h" + +#include "IReferenceCounted.h" +#include "EDriverTypes.h" + +namespace irr +{ + namespace video + { + class IRWBuffer; + class ITexture; + + class IGPUCompute : public virtual IReferenceCounted + { + public: + IGPUCompute() : + DriverType(EDT_NULL) + { + + } + + E_DRIVER_TYPE getDriverType() const { return DriverType; }; + + virtual bool setVariable(s32 id, const f32* floats, int count) = 0; + + virtual s32 getVariableID(const c8* name) = 0; + + virtual void setTexture(int slot, ITexture *texture) = 0; + + virtual void setBuffer(int slot, IRWBuffer *buffer) = 0; + + virtual void dispatch(int threadGroupX, int threadGroupY, int threadGroupZ) = 0; + + protected: + + E_DRIVER_TYPE DriverType; + }; + } +} + +#endif \ No newline at end of file diff --git a/Projects/Irrlicht/Include/IGPUProgrammingServices.h b/Projects/Irrlicht/Include/IGPUProgrammingServices.h index 28717d5ed..97a7fc53f 100644 --- a/Projects/Irrlicht/Include/IGPUProgrammingServices.h +++ b/Projects/Irrlicht/Include/IGPUProgrammingServices.h @@ -10,6 +10,8 @@ #include "EPrimitiveTypes.h" #include "path.h" +#include "IGPUCompute.h" + namespace irr { @@ -23,6 +25,7 @@ namespace video class IVideoDriver; class IShaderConstantSetCallBack; +class IGPUCompute; //! Enumeration for different types of shading languages enum E_GPU_SHADING_LANGUAGE @@ -124,6 +127,14 @@ class IGPUProgrammingServices callback, baseMaterial, userData, shadingLang); } + virtual IGPUCompute* createComputeProgram(const c8* computeShaderProgram, + const c8* computeShaderEntryPointName = "main", + E_COMPUTE_SHADER_TYPE csCompileTarget = ECST_CS_5_0) = 0; + + virtual IGPUCompute* createComputeProgramFromFile(const io::path& computeShaderFileName, + const c8* computeShaderEntryPointName = "main", + E_COMPUTE_SHADER_TYPE csCompileTarget = ECST_CS_5_0) = 0; + //! convenience function for use with many defaults, without geometry shader /** All shader names are set to "main" and compile targets are shader type 1.1. diff --git a/Projects/Irrlicht/Include/IRWBuffer.h b/Projects/Irrlicht/Include/IRWBuffer.h new file mode 100644 index 000000000..3a992dc8a --- /dev/null +++ b/Projects/Irrlicht/Include/IRWBuffer.h @@ -0,0 +1,42 @@ +// Copyright (C) 2020 Pham Hong Duc +// This file is part of the "Skylicht Engine" +// Upgrade GPU Compute Shader feature + +#ifndef __IRR_IRW_BUFFER_H_INCLUDED__ +#define __IRR_IRW_BUFFER_H_INCLUDED__ + +#include "IrrCompileConfig.h" + +#include "IReferenceCounted.h" +#include "EDriverTypes.h" + +namespace irr +{ + namespace video + { + class IRWBuffer : public virtual IReferenceCounted + { + public: + IRWBuffer(ECOLOR_FORMAT format, u32 numElements) : + DriverType(EDT_NULL), + Format(format), + NumElements(numElements) + { + } + + E_DRIVER_TYPE getDriverType() const { return DriverType; }; + + virtual void* lock(bool readOnly) = 0; + + virtual void unlock() = 0; + + protected: + + E_DRIVER_TYPE DriverType; + ECOLOR_FORMAT Format; + u32 NumElements; + }; + } +} + +#endif \ No newline at end of file diff --git a/Projects/Irrlicht/Include/IVideoDriver.h b/Projects/Irrlicht/Include/IVideoDriver.h index 6767201f5..fb8493c58 100644 --- a/Projects/Irrlicht/Include/IVideoDriver.h +++ b/Projects/Irrlicht/Include/IVideoDriver.h @@ -21,6 +21,7 @@ #include "SExposedVideoData.h" #include "IHardwareBuffer.h" +#include "IRWBuffer.h" namespace irr { @@ -1108,6 +1109,15 @@ namespace video const core::position2d& pos, const core::dimension2d& size) =0; + //! Creates a buffer stored on gpu + /** + \param format pixel data. + \param number of pixels + \return The gpu buffer object. + If you no longer need the image, you should call IImage::drop(). + See IReferenceCounted::drop() for more information. */ + virtual IRWBuffer* createRWBuffer(video::ECOLOR_FORMAT format, u32 numElements, void *initialData = NULL) = 0; + //! Event handler for resize events. Only used by the engine internally. /** Used to notify the driver that the window was resized. Usually, there is no need to call this method. */ diff --git a/Projects/Irrlicht/Source/CD3D11Driver.cpp b/Projects/Irrlicht/Source/CD3D11Driver.cpp index 978bd8df8..b584a5116 100644 --- a/Projects/Irrlicht/Source/CD3D11Driver.cpp +++ b/Projects/Irrlicht/Source/CD3D11Driver.cpp @@ -21,7 +21,8 @@ #include "CD3D11Texture.h" #include "CD3D11HardwareBuffer.h" #include "CD3D11VideoRT.h" - +#include "CD3D11RWBuffer.h" +#include "CD3D11GPUCompute.h" inline void unpack_texureBlendFunc(irr::video::E_BLEND_FACTOR &srcFact, irr::video::E_BLEND_FACTOR &dstFact, irr::video::E_MODULATE_FUNC &modulo, irr::u32& alphaSource, const irr::f32 param) @@ -1412,6 +1413,12 @@ namespace irr return new CD3D11TextureCube(this, "TextureCube", imageX1, imageX2, imageY1, imageY2, imageZ1, imageZ2); } + //! creates a buffer stored on gpu + IRWBuffer* CD3D11Driver::createRWBuffer(video::ECOLOR_FORMAT format, u32 numElements, void *initialData) + { + return new CD3D11RWBuffer(this, format, numElements); + } + void CD3D11Driver::setViewPort(const core::rect& area) { core::dimension2du size = getCurrentRenderTargetSize(); @@ -2801,6 +2808,21 @@ namespace irr return id; } + IGPUCompute* CD3D11Driver::createComputeProgram(const c8* computeShaderProgram, + const c8* computeShaderEntryPointName, + E_COMPUTE_SHADER_TYPE csCompileTarget) + { + CD3D11GPUCompute *compute = new CD3D11GPUCompute(this); + + if (compute->compile(computeShaderProgram, computeShaderEntryPointName, csCompileTarget) == true) + { + return compute; + } + + compute->drop(); + return NULL; + } + //! Adds a new material renderer to the VideoDriver, using pixel and/or //! vertex shaders to render geometry. s32 CD3D11Driver::addShaderMaterial(const c8* vertexShaderProgram, diff --git a/Projects/Irrlicht/Source/CD3D11Driver.h b/Projects/Irrlicht/Source/CD3D11Driver.h index d8da27dd2..eb95edd78 100644 --- a/Projects/Irrlicht/Source/CD3D11Driver.h +++ b/Projects/Irrlicht/Source/CD3D11Driver.h @@ -61,6 +61,8 @@ namespace video friend class CD3D11TextureCube; friend class CD3D11TextureArray; friend class CD3D11VideoRT; + friend class CD3D11RWBuffer; + friend class CD3D11GPUCompute; //! constructor CD3D11Driver(const irr::SIrrlichtCreationParameters& params, @@ -220,6 +222,9 @@ namespace video virtual ITexture* getTextureArray(IImage** images, u32 num); + //! creates a buffer stored on gpu + virtual IRWBuffer* createRWBuffer(video::ECOLOR_FORMAT format, u32 numElements, void *initialData = NULL); + //! Clears the ZBuffer. virtual void clearZBuffer(); @@ -412,6 +417,10 @@ namespace video virtual s32 addShaderMaterial(const c8* vertexShaderProgram, const c8* pixelShaderProgram, IShaderConstantSetCallBack* callback, E_MATERIAL_TYPE baseMaterial, s32 userData); + virtual IGPUCompute* createComputeProgram(const c8* computeShaderProgram, + const c8* computeShaderEntryPointName = "main", + E_COMPUTE_SHADER_TYPE csCompileTarget = ECST_CS_5_0); + void draw2D3DVertexPrimitiveList(const void* vertices, u32 vertexCount, u32 pVertexSize, const void* indices, u32 primitiveCount, E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType, E_INDEX_TYPE iType, bool is3D, u32 numInstances = 0); diff --git a/Projects/Irrlicht/Source/CD3D11GPUCompute.cpp b/Projects/Irrlicht/Source/CD3D11GPUCompute.cpp new file mode 100644 index 000000000..12ad25225 --- /dev/null +++ b/Projects/Irrlicht/Source/CD3D11GPUCompute.cpp @@ -0,0 +1,457 @@ +// Copyright (C) 2020 Pham Hong Duc +// This file is part of the "Skylicht Engine" +// Upgrade GPU Compute Shader feature + +#include "pch.h" +#include "IrrCompileConfig.h" +#include "CD3D11Driver.h" +#include "CD3D11Texture.h" +#include "CD3D11RWBuffer.h" +#include "CD3D11GPUCompute.h" + +#ifdef _IRR_COMPILE_WITH_DIRECT3D_11_ + +#include "irrOS.h" + +#include "d3dcompiler.h" + +namespace irr +{ + namespace video + { + CD3D11GPUCompute::CD3D11GPUCompute(CD3D11Driver *driver) : + ComputeShader(NULL), + ShaderBuffer(NULL), + VariableArrayPtr(NULL) + { + DriverType = EDT_DIRECT3D11; + + Device = driver->getExposedVideoData().D3D11.D3DDev11; + if (Device) + { + Device->AddRef(); + Device->GetImmediateContext(&Context); + } + + for (int i = 0; i < NUM_PARAMS_SUPPORT; i++) + { + TextureSlot[i] = NULL; + BufferSlot[i] = NULL; + } + } + + CD3D11GPUCompute::~CD3D11GPUCompute() + { + Device->Release(); + Context->Release(); + + if (ComputeShader != NULL) + ComputeShader->Release(); + + if (ShaderBuffer != NULL) + ShaderBuffer->Release(); + } + + bool CD3D11GPUCompute::compile(const c8* computeShaderProgram, + const c8* computeShaderEntryPointName, + E_COMPUTE_SHADER_TYPE csCompileTarget) + { + ID3D10Blob* errorMsgs = 0; + + ID3DInclude* includer = NULL; + + UINT flags = 0; + +#if !defined(WINDOWS_STORE) + if (csCompileTarget >= ECST_CS_5_0) + flags |= D3D10_SHADER_ENABLE_STRICTNESS; + else + { + flags |= D3D10_SHADER_ENABLE_BACKWARDS_COMPATIBILITY; + csCompileTarget = ECST_CS_4_0; + } + +#ifdef _DEBUG + // These values allow use of PIX and shader debuggers + flags |= D3D10_SHADER_DEBUG; + flags |= D3D10_SHADER_SKIP_OPTIMIZATION; +#else + // These flags allow maximum performance + flags |= D3D10_SHADER_OPTIMIZATION_LEVEL3; +#endif + + flags |= D3D10_SHADER_OPTIMIZATION_LEVEL3; +#endif + + // last macro has to be NULL + core::array macroArray; + + D3D_SHADER_MACRO macro; + macro.Definition = NULL; + macro.Name = NULL; + + macroArray.push_back(macro); + + HRESULT hr = D3DCompile( + computeShaderProgram, + strlen(computeShaderProgram), + "", + ¯oArray[0], + includer, + computeShaderEntryPointName, + COMPUTE_SHADER_TYPE_NAMES[csCompileTarget], + flags, 0, + &ShaderBuffer, + &errorMsgs); + + if (FAILED(hr)) + { + core::stringc errorMsg = "Could not compile shader"; + + if (errorMsgs) + { + errorMsg += ": "; + errorMsg += static_cast(errorMsgs->GetBufferPointer()); + + errorMsgs->Release(); + } + + logFormatError(hr, errorMsg); + + return false; + } +#ifdef _DEBUG + else if (errorMsgs) + { + core::stringc errorMsg = "Shader compilation warning: "; + errorMsg += static_cast(errorMsgs->GetBufferPointer()); + + errorMsgs->Release(); + errorMsgs = NULL; + + os::Printer::log(errorMsg.c_str(), ELL_WARNING); + } +#endif + + if (errorMsgs) + errorMsgs->Release(); + + if (!ShaderBuffer) + return false; + + hr = Device->CreateComputeShader( + ShaderBuffer->GetBufferPointer(), + ShaderBuffer->GetBufferSize(), + NULL, + &ComputeShader); + + if (FAILED(hr)) + { + core::stringc errorMsg = "Could not create computeshader"; + logFormatError(hr, errorMsg); + return false; + } + + initConstant(); + + return true; + } + + s32 CD3D11GPUCompute::getVariableID(const c8* name) + { + const u32 size = VariableArray.size(); + + for (u32 i = 0; i < size; ++i) + { + if (VariableArray[i]->name == name) + return i; + } + + core::stringc s = "HLSL variable to get ID not found: '"; + s += name; + s += "'. Available variables are:"; + os::Printer::log(s.c_str(), ELL_WARNING); + + return -1; + } + + bool CD3D11GPUCompute::setVariable(s32 id, const f32* floats, int count) + { + SShaderVariable* var = VariableArrayPtr[id]; + + if (!var) + return false; + + SShaderBuffer* buff = var->buffer; + + c8* byteData = (c8*)buff->cData; + byteData += var->offset; + + if (var->classType == D3D10_SVC_MATRIX_COLUMNS) + { + // transpose matrix + int numMatrix = count / 16; + + float *m = (float*)byteData; + const float *v = floats; + + for (int i = 0; i < numMatrix; i++) + { + m[0] = v[0]; + m[1] = v[4]; + m[2] = v[8]; + m[3] = v[12]; + + m[4] = v[1]; + m[5] = v[5]; + m[6] = v[9]; + m[7] = v[13]; + + m[8] = v[2]; + m[9] = v[6]; + m[10] = v[10]; + m[11] = v[14]; + + m[12] = v[3]; + m[13] = v[7]; + m[14] = v[11]; + m[15] = v[15]; + + m += 16; + v += 16; + } + } + else + { + memcpy(byteData, floats, count * sizeof(f32)); + } + + return true; + } + + //! uploadVariableToGPU + bool CD3D11GPUCompute::uploadVariableToGPU() + { + for (int i = 0, n = BufferArray.size(); i < n; i++) + { + SShaderBuffer* buff = BufferArray[i]; + + // do it later + D3D11_MAPPED_SUBRESOURCE mappedData; + + HRESULT hr = Context->Map(buff->data, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData); + + if (FAILED(hr)) + { + logFormatError(hr, "Could not map float variable in shader"); + return false; + } + + memcpy(mappedData.pData, buff->cData, buff->size); + + Context->Unmap(buff->data, 0); + } + + return true; + } + + bool CD3D11GPUCompute::initConstant() + { + // D3DXCompile + typedef HRESULT(WINAPI *D3DX11ReflectFunc)(LPCVOID pSrcData, SIZE_T SrcDataSize, REFIID pInterface, void** ppReflector); + + static D3DX11ReflectFunc pFn = 0; + static bool LoadFailed = false; + + if (LoadFailed) + return false; + + ID3D11ShaderReflection* pReflector = NULL; + HRESULT hr = D3DReflect(ShaderBuffer->GetBufferPointer(), ShaderBuffer->GetBufferSize(), IID_ID3D11ShaderReflection, (void**)&pReflector); + + if (FAILED(hr)) + { + logFormatError(hr, "Could not reflect shader"); + return false; + } + + D3D11_SHADER_DESC shaderDesc; + pReflector->GetDesc(&shaderDesc); + + for (u32 i = 0; i < shaderDesc.BoundResources; ++i) + { + D3D11_SHADER_INPUT_BIND_DESC resourceDesc; + pReflector->GetResourceBindingDesc(i, &resourceDesc); + + switch (resourceDesc.Type) + { + case D3D_SIT_CBUFFER: + { + ID3D11ShaderReflectionConstantBuffer* reflectionBuffer = pReflector->GetConstantBufferByName(resourceDesc.Name); + + D3D11_SHADER_BUFFER_DESC bufferDesc; + reflectionBuffer->GetDesc(&bufferDesc); + + SShaderBuffer* sBuffer = createConstantBuffer(bufferDesc); + + if (sBuffer) + { + BufferArray.push_back(sBuffer); + + // add vars to shader + for (u32 j = 0; j < bufferDesc.Variables; j++) + { + ID3D11ShaderReflectionVariable* var = reflectionBuffer->GetVariableByIndex(j); + + D3D11_SHADER_VARIABLE_DESC varDesc; + var->GetDesc(&varDesc); + + D3D11_SHADER_TYPE_DESC typeDesc; + var->GetType()->GetDesc(&typeDesc); + + SShaderVariable* sv = new SShaderVariable(); + sv->name = varDesc.Name; + sv->buffer = sBuffer; + sv->offset = varDesc.StartOffset; + sv->size = varDesc.Size; + sv->baseType = typeDesc.Type; + sv->classType = typeDesc.Class; + + VariableArray.push_back(sv); + } + } + + break; + } + case D3D_SIT_TBUFFER: + { + // same as cbuffer? + break; + } + case D3D_SIT_SAMPLER: + { + break; + } + case D3D_SIT_TEXTURE: + { + break; + } + } + } + + VariableArrayPtr = VariableArray.pointer(); + + pReflector->Release(); + + return true; + } + + SShaderBuffer* CD3D11GPUCompute::createConstantBuffer(D3D11_SHADER_BUFFER_DESC& bufferDesc) + { + SShaderBuffer* sBuffer = NULL; + + // take the same buffer from the other shader if it has the same name + bool found = false; + + for (u32 j = 0; j < BufferArray.size(); ++j) + { + if (BufferArray[j]->name == bufferDesc.Name) + { + sBuffer = BufferArray[j]; + sBuffer->AddRef(); + found = true; + break; + } + } + + // no buffer found so create a new one + if (!sBuffer) + { + sBuffer = new SShaderBuffer(); + sBuffer->name = bufferDesc.Name; + sBuffer->size = bufferDesc.Size; + } + + if (!sBuffer->data) + { + D3D11_BUFFER_DESC cbDesc; + cbDesc.ByteWidth = sBuffer->size; + cbDesc.Usage = D3D11_USAGE_DYNAMIC; + cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + cbDesc.MiscFlags = 0; + cbDesc.StructureByteStride = 0; + + // Create the buffer. + HRESULT hr = Device->CreateBuffer(&cbDesc, NULL, &sBuffer->data); + + if (FAILED(hr)) + { + core::stringc error = "Could not create constant buffer \""; + error += sBuffer->name; + error += "\""; + + logFormatError(hr, error); + + delete sBuffer; + + return NULL; + } + + sBuffer->cData = malloc(sBuffer->size); + } + + return sBuffer; + } + + void CD3D11GPUCompute::setTexture(int slot, ITexture *texture) + { + TextureSlot[slot] = texture; + } + + void CD3D11GPUCompute::setBuffer(int slot, IRWBuffer *buffer) + { + BufferSlot[slot] = buffer; + } + + void CD3D11GPUCompute::dispatch(int threadGroupX, int threadGroupY, int threadGroupZ) + { + Context->CSSetShader(ComputeShader, NULL, 0); + + for (int i = 0; i < NUM_PARAMS_SUPPORT; i++) + { + // Texture resource view + ID3D11ShaderResourceView* views = NULL; + if (TextureSlot[i]) + views = ((CD3D11Texture*)TextureSlot[i])->getShaderResourceView(); + Context->CSSetShaderResources(i, 1, &views); + + // Buffer unorderred access view + ID3D11UnorderedAccessView* unorderedAccessView = NULL; + if (BufferSlot[i]) + unorderedAccessView = ((CD3D11RWBuffer*)BufferSlot[i])->getUnorderedAccessView(); + Context->CSSetUnorderedAccessViews(i, 1, &unorderedAccessView, NULL); + } + + // update constant buffer to GPU + uploadVariableToGPU(); + + u32 size = BufferArray.size(); + if (size > 0) + { + core::array buffs; + buffs.reallocate(size); + + for (u32 i = 0; i < size; ++i) + buffs.push_back(BufferArray[i]->data); + + Context->CSSetConstantBuffers(0, size, &buffs[0]); + } + + // do gpu compute + Context->Dispatch(threadGroupX, threadGroupY, threadGroupZ); + } + } +} + +#endif \ No newline at end of file diff --git a/Projects/Irrlicht/Source/CD3D11GPUCompute.h b/Projects/Irrlicht/Source/CD3D11GPUCompute.h new file mode 100644 index 000000000..4c042f03a --- /dev/null +++ b/Projects/Irrlicht/Source/CD3D11GPUCompute.h @@ -0,0 +1,71 @@ +// Copyright (C) 2020 Pham Hong Duc +// This file is part of the "Skylicht Engine" +// Upgrade GPU Compute Shader feature + +#ifndef __C_DIRECTX11_GPUCOMPUTE_H_INCLUDED__ +#define __C_DIRECTX11_GPUCOMPUTE_H_INCLUDED__ + +#include "IrrCompileConfig.h" + +#ifdef _IRR_WINDOWS_ +#ifdef _IRR_COMPILE_WITH_DIRECT3D_11_ + +#define NUM_PARAMS_SUPPORT 4 + +#include "IGPUCompute.h" + +namespace irr +{ + namespace video + { + class CD3D11Driver; + + class CD3D11GPUCompute : public IGPUCompute + { + protected: + ID3D11Device* Device; + ID3D11DeviceContext* Context; + + ID3D11ComputeShader *ComputeShader; + ID3D10Blob* ShaderBuffer; + + ITexture *TextureSlot[NUM_PARAMS_SUPPORT]; + IRWBuffer *BufferSlot[NUM_PARAMS_SUPPORT]; + + core::array BufferArray; + core::array VariableArray; + SShaderVariable** VariableArrayPtr; + + public: + CD3D11GPUCompute(CD3D11Driver *driver); + + virtual ~CD3D11GPUCompute(); + + bool compile(const c8* computeShaderProgram, + const c8* computeShaderEntryPointName = "main", + E_COMPUTE_SHADER_TYPE csCompileTarget = ECST_CS_5_0); + + bool setVariable(s32 id, const f32* floats, int count); + + s32 getVariableID(const c8* name); + + virtual void setTexture(int slot, ITexture *texture); + + virtual void setBuffer(int slot, IRWBuffer *buffer); + + virtual void dispatch(int threadGroupX, int threadGroupY, int threadGroupZ); + + protected: + + bool initConstant(); + + SShaderBuffer* createConstantBuffer(D3D11_SHADER_BUFFER_DESC& bufferDesc); + + bool uploadVariableToGPU(); + }; + } +} + +#endif +#endif +#endif \ No newline at end of file diff --git a/Projects/Irrlicht/Source/CD3D11RWBuffer.cpp b/Projects/Irrlicht/Source/CD3D11RWBuffer.cpp new file mode 100644 index 000000000..93c42bce9 --- /dev/null +++ b/Projects/Irrlicht/Source/CD3D11RWBuffer.cpp @@ -0,0 +1,113 @@ +// Copyright (C) 2020 Pham Hong Duc +// This file is part of the "Skylicht Engine" +// Upgrade GPU Compute Shader feature + +#include "pch.h" +#include "IrrCompileConfig.h" +#include "CD3D11Driver.h" +#include "CD3D11RWBuffer.h" + +#ifdef _IRR_COMPILE_WITH_DIRECT3D_11_ + +#include "irrOS.h" + +namespace irr +{ + namespace video + { + CD3D11RWBuffer::CD3D11RWBuffer(CD3D11Driver *driver, ECOLOR_FORMAT format, u32 numElements, void *initialData) : + IRWBuffer(format, numElements), + Driver(driver) + { + DriverType = EDT_DIRECT3D11; + + D3DFormat = Driver->getD3DFormatFromColorFormat(format); + u32 bytePerPixel = Driver->getBitsPerPixel(D3DFormat) / 8; + + Device = driver->getExposedVideoData().D3D11.D3DDev11; + if (Device) + { + Device->AddRef(); + Device->GetImmediateContext(&Context); + } + + D3D11_BUFFER_DESC bufferDesc; + bufferDesc.ByteWidth = bytePerPixel * numElements; + bufferDesc.Usage = D3D11_USAGE_DEFAULT; + bufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ; + bufferDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS; + bufferDesc.MiscFlags = 0; + bufferDesc.StructureByteStride = 0; + + if (initialData == NULL) + Device->CreateBuffer(&bufferDesc, NULL, &Buffer); + else + { + // Load initial data + D3D11_SUBRESOURCE_DATA data; + data.pSysMem = initialData; + data.SysMemPitch = 0; + data.SysMemSlicePitch = 0; + + Device->CreateBuffer(&bufferDesc, &data, &Buffer); + } + + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; + srvDesc.Format = D3DFormat; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER; + srvDesc.Buffer.ElementOffset = 0; + srvDesc.Buffer.ElementWidth = numElements; + Device->CreateShaderResourceView(Buffer, &srvDesc, &SRView); + + D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc; + uavDesc.Format = D3DFormat; + uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; + uavDesc.Buffer.FirstElement = 0; + uavDesc.Buffer.Flags = 0; + uavDesc.Buffer.NumElements = numElements; + Device->CreateUnorderedAccessView(Buffer, &uavDesc, &UAView); + } + + CD3D11RWBuffer::~CD3D11RWBuffer() + { + SRView->Release(); + UAView->Release(); + Buffer->Release(); + + Context->Release(); + Device->Release(); + } + + //! Lock function. + void* CD3D11RWBuffer::lock(bool readOnly) + { + if (!Buffer) + return 0; + + if (readOnly) + LastMapDirection = D3D11_MAP_READ; + else + LastMapDirection = (D3D11_MAP)(D3D11_MAP_WRITE | D3D11_MAP_READ); + + // Otherwise, map this buffer + D3D11_MAPPED_SUBRESOURCE mappedData; + HRESULT hr = Context->Map(Buffer, 0, LastMapDirection, 0, &mappedData); + if (FAILED(hr)) + return 0; + + return mappedData.pData; + } + + //! Unlock function. Must be called after a lock() to the buffer. + void CD3D11RWBuffer::unlock() + { + if (!Buffer) + return; + + // Otherwise, unmap this + Context->Unmap(Buffer, 0); + } + } +} + +#endif \ No newline at end of file diff --git a/Projects/Irrlicht/Source/CD3D11RWBuffer.h b/Projects/Irrlicht/Source/CD3D11RWBuffer.h new file mode 100644 index 000000000..6e62b6b93 --- /dev/null +++ b/Projects/Irrlicht/Source/CD3D11RWBuffer.h @@ -0,0 +1,62 @@ +// Copyright (C) 2020 Pham Hong Duc +// This file is part of the "Skylicht Engine" +// Upgrade GPU Compute Shader feature + +#ifndef __C_DIRECTX11_RWBUFFER_H_INCLUDED__ +#define __C_DIRECTX11_RWBUFFER_H_INCLUDED__ + +#include "IrrCompileConfig.h" + +#ifdef _IRR_WINDOWS_ +#ifdef _IRR_COMPILE_WITH_DIRECT3D_11_ + +#include "IRWBuffer.h" + +namespace irr +{ + namespace video + { + class CD3D11Driver; + + class CD3D11RWBuffer : public IRWBuffer + { + public: + CD3D11RWBuffer(CD3D11Driver *driver, ECOLOR_FORMAT format, u32 numElements, void *initialData = NULL); + + ~CD3D11RWBuffer(); + + ID3D11ShaderResourceView *getShaderResourceView() + { + return SRView; + } + + ID3D11UnorderedAccessView *getUnorderedAccessView() + { + return UAView; + } + + void* lock(bool readOnly); + + void unlock(); + + protected: + + CD3D11Driver *Driver; + + ID3D11Device* Device; + ID3D11DeviceContext* Context; + + ID3D11Buffer* Buffer; + ID3D11ShaderResourceView* SRView; + ID3D11UnorderedAccessView* UAView; + + DXGI_FORMAT D3DFormat; + + D3D11_MAP LastMapDirection; + }; +} +} + +#endif +#endif +#endif \ No newline at end of file diff --git a/Projects/Irrlicht/Source/CNullDriver.cpp b/Projects/Irrlicht/Source/CNullDriver.cpp index fd526ea9a..ed9fc627a 100644 --- a/Projects/Irrlicht/Source/CNullDriver.cpp +++ b/Projects/Irrlicht/Source/CNullDriver.cpp @@ -786,7 +786,6 @@ void CNullDriver::addTexture(video::ITexture* texture) } } - //! looks if the image is already loaded video::ITexture* CNullDriver::findTexture(const io::path& filename) { @@ -842,7 +841,10 @@ ITexture* CNullDriver::addTexture(const core::dimension2d& size, return t; } - +IRWBuffer* CNullDriver::createRWBuffer(video::ECOLOR_FORMAT format, u32 numElements, void *initialData) +{ + return NULL; +} //! returns a device dependent texture from a software surface (IImage) //! THIS METHOD HAS TO BE OVERRIDDEN BY DERIVED DRIVERS WITH OWN TEXTURES @@ -2479,6 +2481,54 @@ s32 CNullDriver::addHighLevelShaderMaterialFromFiles( return result; } +IGPUCompute* CNullDriver::createComputeProgramFromFile(const io::path& computeShaderFileName, + const c8* computeShaderEntryPointName, + E_COMPUTE_SHADER_TYPE csCompileTarget) +{ + io::IReadFile* csfile = 0; + + if (computeShaderFileName.size()) + { + csfile = FileSystem->createAndOpenFile(computeShaderFileName); + if (!csfile) + { + os::Printer::log("Could not open vertex shader program file", + computeShaderFileName, ELL_WARNING); + return NULL; + } + } + + c8 *cs = NULL; + + const long size = csfile->getSize(); + if (size) + { + cs = new c8[size + 1]; + csfile->read(cs, size); + cs[size] = 0; + } + + IGPUCompute *result = NULL; + + if (cs != NULL) + { + result = createComputeProgram(cs, computeShaderEntryPointName, csCompileTarget); + delete []cs; + } + + if (csfile) + csfile->drop(); + + return result; +} + +IGPUCompute* CNullDriver::createComputeProgram(const c8* computeShaderProgram, + const c8* computeShaderEntryPointName, + E_COMPUTE_SHADER_TYPE csCompileTarget) +{ + os::Printer::log("Compute shader not implemented yet in this driver, sorry."); + return NULL; +} //! Adds a new material renderer to the VideoDriver, using pixel and/or //! vertex shaders to render geometry. @@ -2677,7 +2727,6 @@ bool CNullDriver::setClipPlane(u32 index, const core::plane3df& plane, bool enab return false; } - //! Enable/disable a clipping plane. void CNullDriver::enableClipPlane(u32 index, bool enable) { diff --git a/Projects/Irrlicht/Source/CNullDriver.h b/Projects/Irrlicht/Source/CNullDriver.h index 943d028d9..3ce1b7da4 100644 --- a/Projects/Irrlicht/Source/CNullDriver.h +++ b/Projects/Irrlicht/Source/CNullDriver.h @@ -145,6 +145,9 @@ namespace video bool clearZBuffer, SColor color) _IRR_OVERRIDE_; + //! creates a buffer stored on gpu + virtual IRWBuffer* createRWBuffer(video::ECOLOR_FORMAT format, u32 numElements, void *initialData = NULL) _IRR_OVERRIDE_; + //! sets a viewport virtual void setViewPort(const core::rect& area) _IRR_OVERRIDE_; @@ -491,6 +494,14 @@ namespace video E_MATERIAL_TYPE baseMaterial = video::EMT_SOLID, s32 userData = 0, E_GPU_SHADING_LANGUAGE shadingLang = EGSL_DEFAULT) _IRR_OVERRIDE_; + virtual IGPUCompute* createComputeProgram(const c8* computeShaderProgram, + const c8* computeShaderEntryPointName = "main", + E_COMPUTE_SHADER_TYPE csCompileTarget = ECST_CS_5_0) _IRR_OVERRIDE_; + + virtual IGPUCompute* createComputeProgramFromFile(const io::path& computeShaderFileName, + const c8* computeShaderEntryPointName = "main", + E_COMPUTE_SHADER_TYPE csCompileTarget = ECST_CS_5_0) _IRR_OVERRIDE_; + //! Returns a pointer to the mesh manipulator. virtual scene::IMeshManipulator* getMeshManipulator() _IRR_OVERRIDE_; @@ -660,6 +671,7 @@ namespace video core::array Textures; core::array VRTs; + core::array RWBuffers; struct SOccQuery { diff --git a/Projects/Skylicht/Engine/Source/Culling/CCullingSystem.cpp b/Projects/Skylicht/Engine/Source/Culling/CCullingSystem.cpp index a5b18a8e5..884c9343e 100644 --- a/Projects/Skylicht/Engine/Source/Culling/CCullingSystem.cpp +++ b/Projects/Skylicht/Engine/Source/Culling/CCullingSystem.cpp @@ -91,6 +91,8 @@ namespace Skylicht CWorldInverseTransformData **invTransforms = m_invTransforms.pointer(); IRenderPipeline *rp = entityManager->getRenderPipeline(); + if (rp == NULL) + return; core::matrix4 invTrans; diff --git a/Projects/Skylicht/Lightmapper/Source/Lightmapper/CGPUBaker.cpp b/Projects/Skylicht/Lightmapper/Source/Lightmapper/CGPUBaker.cpp new file mode 100644 index 000000000..c370d0916 --- /dev/null +++ b/Projects/Skylicht/Lightmapper/Source/Lightmapper/CGPUBaker.cpp @@ -0,0 +1,180 @@ +/* +!@ +MIT License + +Copyright (c) 2020 Skylicht Technology CO., LTD + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +This file is part of the "Skylicht Engine". +https://github.com/skylicht-lab/skylicht-engine +!# +*/ + +#include "pch.h" +#include "CGPUBaker.h" +#include "CLightmapper.h" + +#include "RenderPipeline/CBaseRP.h" + +namespace Skylicht +{ + namespace Lightmapper + { + CGPUBaker::CGPUBaker() + { + IVideoDriver *driver = getVideoDriver(); + + // load compute shader + if (driver->getDriverType() == video::EDT_DIRECT3D11) + { + // output buffer + m_shBuffer = driver->createRWBuffer(video::ECF_A32B32G32R32F, MAX_NUM_THREAD * NUM_FACES * 9); + + // gpu compute program + m_shCompute = driver->getGPUProgrammingServices()->createComputeProgramFromFile("BuiltIn/Shader/Compute/HLSL/IrradianceSH.hlsl"); + + // uniform matrix + m_tangentToSpaceData = new float[MAX_NUM_THREAD * NUM_FACES * 16]; + } + else + { + m_shBuffer = NULL; + m_shCompute = NULL; + m_tangentToSpaceData = NULL; + } + } + + CGPUBaker::~CGPUBaker() + { + if (m_shBuffer != NULL) + m_shBuffer->drop(); + + if (m_shCompute != NULL) + m_shCompute->drop(); + + if (m_tangentToSpaceData != NULL) + delete m_tangentToSpaceData; + } + + bool CGPUBaker::canUseGPUBaker() + { + if (getVideoDriver()->getDriverType() == video::EDT_DIRECT3D11 && + m_shBuffer != NULL && + m_shCompute != NULL) + return true; + + return false; + } + + void CGPUBaker::computeSH(int count, int numFace) + { + // render target size + u32 rtSize = CLightmapper::getHemisphereBakeSize(); + + // set radiance as texture0 + m_shCompute->setTexture(0, m_radiance); + + // set buffer + m_shCompute->setBuffer(0, m_shBuffer); + + int groupThreadSize = 16; + + int numCell = rtSize / groupThreadSize; + + // clear sh value + for (int tid = 0; tid < count; tid++) + m_sh[tid].zero(); + + // set const buffer + s32 uToTangentSpace = m_shCompute->getVariableID("uToTangentSpace"); + if (uToTangentSpace >= 0) + { + for (int i = 0; i < MAX_NUM_THREAD * NUM_FACES; i++) + memcpy(&m_tangentToSpaceData[i * 16], m_toTangentSpace[i].pointer(), sizeof(float) * 16); + + // update compute constance + m_shCompute->setVariable(uToTangentSpace, m_tangentToSpaceData, MAX_NUM_THREAD * NUM_FACES * 16); + } + + for (int loopY = 0; loopY < numCell; loopY++) + { + for (int loopX = 0; loopX < numCell; loopX++) + { + s32 uPixelOffset = m_shCompute->getVariableID("uPixelOffset"); + if (uPixelOffset >= 0) + { + core::vector2df offset; + offset.X = (float)(loopX * groupThreadSize); + offset.Y = (float)(loopY * groupThreadSize); + m_shCompute->setVariable(uPixelOffset, &offset.X, 2); + } + + s32 uFaceSize = m_shCompute->getVariableID("uFaceSize"); + if (uFaceSize >= 0) + { + core::vector2df faceSize; + faceSize.X = (float)rtSize; + faceSize.Y = (float)rtSize; + m_shCompute->setVariable(uFaceSize, &faceSize.X, 2); + } + + // run thread + m_shCompute->dispatch(count, numFace, 1); + } + } + + // get result buffer data + video::SVec4 *data = (video::SVec4*)m_shBuffer->lock(true); + + // copy SH value compute from GPU to data + for (int tid = 0; tid < count; tid++) + { + core::vector3df shResult[9]; + + // sum sh each face + for (int fid = 0; fid < numFace; fid++) + { + video::SVec4 *computeResult = &data[(tid * NUM_FACES + fid) * 9]; + + for (int i = 0; i < 9; i++) + { + core::vector3df computeSH; + + computeSH.X = computeResult[i].X; + computeSH.Y = computeResult[i].Y; + computeSH.Z = computeResult[i].Z; + + shResult[i] += computeSH; + } + } + + core::vector3df* shValue = m_sh[tid].getValue(); + + for (int i = 0; i < 9; i++) + shValue[i] += shResult[i]; + } + + m_shBuffer->unlock(); + + // finalWeight is weight for 1 pixel on Sphere + // S = 4 * PI * R^2 + float finalWeight = (4.0f * 3.14159f) / (m_weightSum * numFace); + for (int tid = 0; tid < count; tid++) + { + m_sh[tid] *= finalWeight; + } + } + } +} \ No newline at end of file diff --git a/Projects/Skylicht/Lightmapper/Source/Lightmapper/CGPUBaker.h b/Projects/Skylicht/Lightmapper/Source/Lightmapper/CGPUBaker.h new file mode 100644 index 000000000..8bbfdfea5 --- /dev/null +++ b/Projects/Skylicht/Lightmapper/Source/Lightmapper/CGPUBaker.h @@ -0,0 +1,56 @@ +/* +!@ +MIT License + +Copyright (c) 2020 Skylicht Technology CO., LTD + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +This file is part of the "Skylicht Engine". +https://github.com/skylicht-lab/skylicht-engine +!# +*/ + +#pragma once + +#include "CSH9.h" +#include "Camera/CCamera.h" +#include "RenderPipeline/IRenderPipeline.h" +#include "Entity/CEntityManager.h" + +#include "CMTBaker.h" + +namespace Skylicht +{ + namespace Lightmapper + { + class CGPUBaker : public CMTBaker + { + protected: + IGPUCompute *m_shCompute; + + IRWBuffer *m_shBuffer; + + float *m_tangentToSpaceData; + public: + CGPUBaker(); + + virtual ~CGPUBaker(); + + bool canUseGPUBaker(); + + virtual void computeSH(int count, int numFace); + }; + } +} \ No newline at end of file diff --git a/Projects/Skylicht/Lightmapper/Source/Lightmapper/CLightmapper.cpp b/Projects/Skylicht/Lightmapper/Source/Lightmapper/CLightmapper.cpp index 6d61f7cf0..d583dfdf1 100644 --- a/Projects/Skylicht/Lightmapper/Source/Lightmapper/CLightmapper.cpp +++ b/Projects/Skylicht/Lightmapper/Source/Lightmapper/CLightmapper.cpp @@ -29,12 +29,13 @@ namespace Skylicht { namespace Lightmapper { - int CLightmapper::s_numThread = 120; + int CLightmapper::s_numThread = 128; int CLightmapper::s_hemisphereBakeSize = 128; CLightmapper::CLightmapper() : m_singleBaker(NULL), - m_multiBaker(NULL) + m_multiBaker(NULL), + m_gpuBaker(NULL) { } @@ -46,20 +47,34 @@ namespace Skylicht if (m_multiBaker != NULL) delete m_multiBaker; + + if (m_gpuBaker != NULL) + delete m_gpuBaker; } void CLightmapper::initBaker(u32 hemisphereBakeSize) { + // adjust size + u32 size = 16; + while (size < hemisphereBakeSize && size <= 128) + { + size = size * 2; + } + if (m_singleBaker != NULL) delete m_singleBaker; if (m_multiBaker != NULL) delete m_multiBaker; - s_hemisphereBakeSize = hemisphereBakeSize; + if (m_gpuBaker != NULL) + delete m_gpuBaker; + + s_hemisphereBakeSize = size; m_singleBaker = new CBaker(); m_multiBaker = new CMTBaker(); + m_gpuBaker = new CGPUBaker(); } const CSH9& CLightmapper::bakeAtPosition( @@ -90,14 +105,21 @@ namespace Skylicht int numFace) { out.clear(); - + if (m_multiBaker == NULL) { os::Printer::log("[CLightmapper::bakeAtPosition] Need call initBaker first"); return; } - int maxMT = m_multiBaker->getMaxMT(); + // default use multi thread bakder + CMTBaker *baker = m_multiBaker; + + // switch gpu if supported + if (m_gpuBaker->canUseGPUBaker() == true) + baker = m_gpuBaker; + + int maxMT = baker->getMaxMT(); int current = 0; while (current < count) @@ -106,7 +128,7 @@ namespace Skylicht numMT = core::min_(numMT, maxMT); // bake and get SH result - m_multiBaker->bake(camera, + baker->bake(camera, rp, entityMgr, position + current, @@ -117,7 +139,7 @@ namespace Skylicht numFace); for (int i = 0; i < numMT; i++) - out.push_back(m_multiBaker->getSH(i)); + out.push_back(baker->getSH(i)); current += numMT; } diff --git a/Projects/Skylicht/Lightmapper/Source/Lightmapper/CLightmapper.h b/Projects/Skylicht/Lightmapper/Source/Lightmapper/CLightmapper.h index c260436a2..0748e6e1c 100644 --- a/Projects/Skylicht/Lightmapper/Source/Lightmapper/CLightmapper.h +++ b/Projects/Skylicht/Lightmapper/Source/Lightmapper/CLightmapper.h @@ -27,6 +27,7 @@ This file is part of the "Skylicht Engine". #include "Utils/CGameSingleton.h" #include "CBaker.h" #include "CMTBaker.h" +#include "CGPUBaker.h" #include "Components/Probe/CLightProbe.h" namespace Skylicht @@ -42,6 +43,7 @@ namespace Skylicht protected: CBaker *m_singleBaker; CMTBaker *m_multiBaker; + CGPUBaker *m_gpuBaker; CSH9 m_temp; diff --git a/Projects/Skylicht/Lightmapper/Source/Lightmapper/CMTBaker.cpp b/Projects/Skylicht/Lightmapper/Source/Lightmapper/CMTBaker.cpp index e83ea9703..d553d1345 100644 --- a/Projects/Skylicht/Lightmapper/Source/Lightmapper/CMTBaker.cpp +++ b/Projects/Skylicht/Lightmapper/Source/Lightmapper/CMTBaker.cpp @@ -53,6 +53,7 @@ namespace Skylicht { IVideoDriver *driver = getVideoDriver(); + // render radiance // apply projection camera->setAspect(1.0f); camera->setFOV(90.0f); @@ -60,8 +61,6 @@ namespace Skylicht // clear rtt getVideoDriver()->setRenderTarget(m_radiance, true, true); - core::matrix4 toTangentSpace[MAX_NUM_THREAD][NUM_FACES]; - u32 rtSize = CLightmapper::getHemisphereBakeSize(); for (int tid = 0; tid < count; tid++) @@ -76,8 +75,10 @@ namespace Skylicht getWorldView(normal[tid], tangent[tid], binormal[tid], position[tid], face, cameraWorld); // to tangent space - toTangentSpace[tid][face] = cameraWorld; - setRow(toTangentSpace[tid][face], 3, core::vector3df(0.0f, 0.0f, 0.0f), 1.0f); + m_toTangentSpace[tid * NUM_FACES + face] = cameraWorld; + + // remove position + setRow(m_toTangentSpace[tid * NUM_FACES + face], 3, core::vector3df(0.0f, 0.0f, 0.0f), 1.0f); // camera world camera->getGameObject()->getTransform()->setMatrixTransform(cameraWorld); @@ -101,6 +102,15 @@ namespace Skylicht driver->setRenderTarget(NULL, false, false); + // compute sh from radiance + computeSH(count, numFace); + } + + void CMTBaker::computeSH(int count, int numFace) + { + // render target size + u32 rtSize = CLightmapper::getHemisphereBakeSize(); + // Cubemap to SH u8 *imageData = (u8*)m_radiance->lock(video::ETLM_READ_ONLY); u32 bpp = 4; @@ -173,7 +183,8 @@ namespace Skylicht dirTS.X = u; dirTS.Y = v; dirTS.Z = 1.0f; - toTangentSpace[tid][face].rotateVect(dirTS); + + m_toTangentSpace[tid * NUM_FACES + face].rotateVect(dirTS); dirTS.normalize(); m_sh[tid].projectAddOntoSH(dirTS, color); @@ -194,10 +205,10 @@ namespace Skylicht /* static int t = 0; static bool test = true; - if (CDeferredRP::isEnableRenderIndirect() == true && test == true) + if (test == true) { char filename[512]; - sprintf(filename, "C:\\SVN\\test_%d.png", t); + sprintf(filename, "test_%d.png", t); CBaseRP::saveFBOToFile(m_radiance, filename); test = true; } diff --git a/Projects/Skylicht/Lightmapper/Source/Lightmapper/CMTBaker.h b/Projects/Skylicht/Lightmapper/Source/Lightmapper/CMTBaker.h index 7d4d9ffde..39cf53540 100644 --- a/Projects/Skylicht/Lightmapper/Source/Lightmapper/CMTBaker.h +++ b/Projects/Skylicht/Lightmapper/Source/Lightmapper/CMTBaker.h @@ -31,7 +31,7 @@ This file is part of the "Skylicht Engine". #include "CBaker.h" -#define MAX_NUM_THREAD 120 +#define MAX_NUM_THREAD 128 namespace Skylicht { @@ -44,6 +44,8 @@ namespace Skylicht CSH9 m_sh[MAX_NUM_THREAD]; + core::matrix4 m_toTangentSpace[MAX_NUM_THREAD * NUM_FACES]; + float m_weightSum; public: @@ -51,7 +53,7 @@ namespace Skylicht virtual ~CMTBaker(); - void bake(CCamera *camera, + virtual void bake(CCamera *camera, IRenderPipeline* rp, CEntityManager* entityMgr, const core::vector3df* position, @@ -61,6 +63,8 @@ namespace Skylicht int count, int numFace); + virtual void computeSH(int count, int numFace); + inline int getMaxMT() { return MAX_NUM_THREAD; diff --git a/Samples/LightmapUV/Source/SampleLightmapUV.cpp b/Samples/LightmapUV/Source/SampleLightmapUV.cpp index 13447ac16..f9f6dd41f 100644 --- a/Samples/LightmapUV/Source/SampleLightmapUV.cpp +++ b/Samples/LightmapUV/Source/SampleLightmapUV.cpp @@ -85,9 +85,12 @@ void SampleLightmapUV::onInitApp() core::vector3df direction = core::vector3df(-2.0f, -7.0f, -1.5f); lightTransform->setOrientation(direction, CTransform::s_oy); - // 3D model + // 3D model +#ifdef LIGHTMAP_SPONZA + m_model = CMeshManager::getInstance()->loadModel("Sponza/Sponza.dae", "Sponza/Textures"); +#else m_model = CMeshManager::getInstance()->loadModel("SampleModels/Gazebo/gazebo.obj", ""); - // m_model = CMeshManager::getInstance()->loadModel("Sponza/Sponza.dae", "Sponza/Textures"); +#endif if (m_model != NULL) { @@ -163,7 +166,7 @@ void SampleLightmapUV::onInitApp() void SampleLightmapUV::runThread() { // generate uv in thread - m_unwrap.generate(2048, 5.0f); + m_unwrap.generate(2048, 0.5f); m_unwrap.generateUVImage(); // write to bin folder output layout uv @@ -227,18 +230,18 @@ void SampleLightmapUV::updateMeshUV() } } - // test exporter - /* + // Exporter result +#ifdef LIGHTMAP_SPONZA CMeshManager::getInstance()->exportModel( m_renderMesh->getEntities().pointer(), m_renderMesh->getEntities().size(), "../Assets/Sponza/Sponza.smesh"); - */ - +#else CMeshManager::getInstance()->exportModel( m_renderMesh->getEntities().pointer(), m_renderMesh->getEntities().size(), "../Assets/SampleModels/Gazebo/gazebo.smesh"); +#endif // Update material core::array arrayTexture; diff --git a/Samples/LightmapUV/Source/SampleLightmapUV.h b/Samples/LightmapUV/Source/SampleLightmapUV.h index 3ee6d6d27..ebf8a7ec7 100644 --- a/Samples/LightmapUV/Source/SampleLightmapUV.h +++ b/Samples/LightmapUV/Source/SampleLightmapUV.h @@ -5,6 +5,8 @@ #include "UnwrapUV/CUnwrapUV.h" +#define LIGHTMAP_SPONZA + class SampleLightmapUV : public IApplicationEventReceiver, public SkylichtSystem::IThreadCallback diff --git a/Samples/Lightmapping/Source/CViewBakeLightmap.cpp b/Samples/Lightmapping/Source/CViewBakeLightmap.cpp index f16974b3a..87245b4e8 100644 --- a/Samples/Lightmapping/Source/CViewBakeLightmap.cpp +++ b/Samples/Lightmapping/Source/CViewBakeLightmap.cpp @@ -20,7 +20,9 @@ CViewBakeLightmap::CViewBakeLightmap() : m_timeSpentFromLastSave(0), m_numRenderers(0), m_numIndices(0), - m_numVertices(0) + m_numVertices(0), + m_guiObject(NULL), + m_font(NULL) { for (int i = 0; i < MAX_LIGHTMAP_ATLAS; i++) m_lmRasterize[i] = NULL; @@ -28,9 +30,11 @@ CViewBakeLightmap::CViewBakeLightmap() : CViewBakeLightmap::~CViewBakeLightmap() { - m_guiObject->remove(); + if (m_guiObject != NULL) + m_guiObject->remove(); - delete m_font; + if (m_font != NULL) + delete m_font; for (int i = 0; i < MAX_LIGHTMAP_ATLAS; i++) { @@ -68,6 +72,14 @@ int CViewBakeLightmap::getRasterisationIndex(Lightmapper::CRasterisation *raster void CViewBakeLightmap::onInit() { + /* + // enable render indirect + CDeferredRP::enableRenderIndirect(false); + // switch to demo view + CViewManager::getInstance()->getLayer(0)->changeView(); + return; + */ + CContext *context = CContext::getInstance(); CZone *zone = context->getActiveZone(); CEntityManager *entityMgr = zone->getEntityManager(); @@ -75,6 +87,16 @@ void CViewBakeLightmap::onInit() // set default 128px for quality CLightmapper::getInstance()->initBaker(128); + // force update and render to compute transform (1 frame) + { + context->getScene()->update(); + context->getRenderPipeline()->render( + NULL, + context->getActiveCamera(), + context->getScene()->getEntityManager(), + core::recti(0, 0, 0, 0)); + } + // get all render mesh in zone m_renderMesh = zone->getComponentsInChild(false); for (CRenderMesh *renderMesh : m_renderMesh) @@ -223,7 +245,7 @@ void CViewBakeLightmap::onUpdate() vertices[v3].Tangent }; - for (int i = 0; i < 4; i++) + for (int i = 0; i < 3; i++) { transform.transformVect(positions[i]); transform.rotateVect(normals[i]); @@ -231,11 +253,18 @@ void CViewBakeLightmap::onUpdate() } int lmIndex = (int)vertices[v1].Lightmap.Z; - m_currentRasterisation = createGetLightmapRasterisation(lmIndex); - - m_pixel = m_currentRasterisation->setTriangle(positions, uvs, normals, tangents, pass); - - m_lastTris = m_currentTris; + if (lmIndex >= 0) + { + m_currentRasterisation = createGetLightmapRasterisation(lmIndex); + m_pixel = m_currentRasterisation->setTriangle(positions, uvs, normals, tangents, pass); + m_lastTris = m_currentTris; + } + else + { + // skip this triangle + m_currentTris++; + continue; + } } core::vector3df outPos; @@ -459,6 +488,21 @@ void CViewBakeLightmap::saveProgress() io::IWriteFile *file = getIrrlichtDevice()->getFileSystem()->createAndWriteFile("LightmapProgress.dat"); file->write(stream->getData(), stream->getSize()); file->drop(); + + // write current output to review + core::dimension2du size(m_lightmapSize, m_lightmapSize); + char outFileName[512]; + IVideoDriver *driver = getVideoDriver(); + + for (int i = 0; i < m_numberRasterize; i++) + { + unsigned char *data = m_lmRasterize[i]->getLightmapData(); + + IImage *img = driver->createImageFromData(video::ECF_R8G8B8, size, data); + sprintf(outFileName, "LightMapRasterize_review_bounce_%d_%d.png", m_lightBounce, i); + driver->writeImageToFile(img, outFileName); + img->drop(); + } } void CViewBakeLightmap::loadProgress() diff --git a/Samples/Lightmapping/Source/CViewBakeLightmap.h b/Samples/Lightmapping/Source/CViewBakeLightmap.h index 767230071..5f2ce0501 100644 --- a/Samples/Lightmapping/Source/CViewBakeLightmap.h +++ b/Samples/Lightmapping/Source/CViewBakeLightmap.h @@ -9,6 +9,8 @@ #define MAX_LIGHTMAP_ATLAS 40 +#define LIGHTMAP_SPONZA + class CViewBakeLightmap : public CView { protected: diff --git a/Samples/Lightmapping/Source/CViewInit.cpp b/Samples/Lightmapping/Source/CViewInit.cpp index fa9cdd0cf..55d841948 100644 --- a/Samples/Lightmapping/Source/CViewInit.cpp +++ b/Samples/Lightmapping/Source/CViewInit.cpp @@ -6,10 +6,7 @@ #include "SkyDome/CSkyDome.h" -CViewInit::CViewInit() : - m_initState(CViewInit::DownloadBundles), - m_getFile(NULL), - m_downloaded(0) +CViewInit::CViewInit() { } @@ -33,6 +30,7 @@ void CViewInit::onInit() app->getFileSystem()->addFileArchive(app->getBuiltInPath("BuiltIn.zip"), false, false); app->getFileSystem()->addFileArchive(app->getBuiltInPath("Common.zip"), false, false); app->getFileSystem()->addFileArchive(app->getBuiltInPath("SampleModels.zip"), false, false); + app->getFileSystem()->addFileArchive(app->getBuiltInPath("Sponza.zip"), false, false); // load basic shader CShaderManager *shaderMgr = CShaderManager::getInstance(); @@ -80,17 +78,55 @@ void CViewInit::onInit() skyDome->setData(skyDomeTexture, SColor(255, 255, 255, 255)); } +#ifdef LIGHTMAP_SPONZA // lighting CGameObject *lightObj = zone->createEmptyObject(); CDirectionalLight *directionalLight = lightObj->addComponent(); CTransformEuler *lightTransform = lightObj->getTransformEuler(); lightTransform->setPosition(core::vector3df(2.0f, 2.0f, 2.0f)); + core::vector3df direction = core::vector3df(-2.0f, -7.0f, -1.5f); + lightTransform->setOrientation(direction, CTransform::s_oy); + + core::vector3df pointLightPosition[] = { + {-11.19f, 2.4f, 4.01f}, + {-11.2f, 2.4f, -4.5f}, + {12.03f, 2.4f, 4.04f}, + {12.01f, 2.4f, -4.47f}, + {6.18f, 1.6f, -2.2f}, + {6.18f, 1.6f, 1.43f}, + {-4.89f, 1.6f, -2.17f}, + {-4.89f, 1.6f, 1.42f}, + }; + + for (int i = 0; i < 8; i++) + { + CGameObject *pointLightObj = zone->createEmptyObject(); + + CPointLight *pointLight = pointLightObj->addComponent(); + pointLight->setShadow(true); + + if (i >= 4) + pointLight->setRadius(3.0f); + else + pointLight->setRadius(6.0f); + + CTransformEuler *pointLightTransform = pointLightObj->getTransformEuler(); + pointLightTransform->setPosition(pointLightPosition[i]); + } + + CEntityPrefab *model = CMeshManager::getInstance()->loadModel("Sponza/Sponza.smesh", NULL, true); +#else + CGameObject *lightObj = zone->createEmptyObject(); + CDirectionalLight *directionalLight = lightObj->addComponent(); + CTransformEuler *lightTransform = lightObj->getTransformEuler(); + lightTransform->setPosition(core::vector3df(2.0f, 2.0f, 2.0f)); + core::vector3df direction = core::vector3df(4.0f, -6.0f, -4.5f); lightTransform->setOrientation(direction, CTransform::s_oy); - // gazebo object CEntityPrefab *model = CMeshManager::getInstance()->loadModel("SampleModels/Gazebo/gazebo.smesh", "LightmapUV"); +#endif if (model != NULL) { @@ -100,9 +136,18 @@ void CViewInit::onInit() CRenderMesh *renderMesh = gazeboObj->addComponent(); renderMesh->initFromPrefab(model); +#ifdef LIGHTMAP_SPONZA + std::vector textureFolders; + textureFolders.push_back("Sponza/Textures"); + + // load material + ArrayMaterial& materials = CMaterialManager::getInstance()->loadMaterial("Sponza/Sponza.xml", true, textureFolders); +#else + // init default material ArrayMaterial materials = CMaterialManager::getInstance()->initDefaultMaterial(model); for (CMaterial *material : materials) material->changeShader("BuiltIn/Shader/SpecularGlossiness/Deferred/Color.xml"); +#endif renderMesh->initMaterial(materials); } @@ -128,90 +173,11 @@ void CViewInit::onDestroy() void CViewInit::onUpdate() { CContext *context = CContext::getInstance(); + CScene *scene = context->getScene(); + if (scene != NULL) + scene->update(); - switch (m_initState) - { - case CViewInit::DownloadBundles: - { - io::IFileSystem* fileSystem = getApplication()->getFileSystem(); - - std::vector listBundles; - listBundles.push_back("Common.zip"); - listBundles.push_back("SampleModels.zip"); - -#ifdef __EMSCRIPTEN__ - const char *filename = listBundles[m_downloaded].c_str(); - - if (m_getFile == NULL) - { - m_getFile = new CGetFileURL(filename, filename); - m_getFile->download(CGetFileURL::Get); - - char log[512]; - sprintf(log, "Download asset: %s", filename); - os::Printer::log(log); - } - else - { - if (m_getFile->getState() == CGetFileURL::Finish) - { - // [bundles].zip - fileSystem->addFileArchive(filename, false, false); - - if (++m_downloaded >= listBundles.size()) - m_initState = CViewInit::InitScene; - else - { - delete m_getFile; - m_getFile = NULL; - } - } - else if (m_getFile->getState() == CGetFileURL::Error) - { - // retry download - delete m_getFile; - m_getFile = NULL; - } - } -#else - - for (std::string& bundle : listBundles) - { - const char *r = bundle.c_str(); -#if defined(WINDOWS_STORE) - fileSystem->addFileArchive(getBuiltInPath(r), false, false); -#elif defined(MACOS) - fileSystem->addFileArchive(getBuiltInPath(r), false, false); -#else - fileSystem->addFileArchive(r, false, false); -#endif - } - - m_initState = CViewInit::InitScene; -#endif - } - break; - case CViewInit::InitScene: - { - initScene(); - m_initState = CViewInit::Finished; - } - break; - case CViewInit::Error: - { - // todo nothing with black screen - } - break; - default: - { - CScene *scene = context->getScene(); - if (scene != NULL) - scene->update(); - - CViewManager::getInstance()->getLayer(0)->changeView(); - } - break; - } + CViewManager::getInstance()->getLayer(0)->changeView(); } void CViewInit::onRender() diff --git a/Samples/Lightmapping/Source/CViewInit.h b/Samples/Lightmapping/Source/CViewInit.h index eaa2e8ecb..1c39b8398 100644 --- a/Samples/Lightmapping/Source/CViewInit.h +++ b/Samples/Lightmapping/Source/CViewInit.h @@ -15,12 +15,6 @@ class CViewInit : public CView Finished }; -protected: - CGetFileURL *m_getFile; - - EInitState m_initState; - unsigned int m_downloaded; - protected: io::path getBuiltInPath(const char *name); diff --git a/Samples/LightmappingVertex/Source/CViewBakeLightmap.cpp b/Samples/LightmappingVertex/Source/CViewBakeLightmap.cpp index 63ab89a60..1d2d22fc0 100644 --- a/Samples/LightmappingVertex/Source/CViewBakeLightmap.cpp +++ b/Samples/LightmappingVertex/Source/CViewBakeLightmap.cpp @@ -207,6 +207,7 @@ void CViewBakeLightmap::onUpdate() } CViewManager::getInstance()->getLayer(0)->changeView(); + return; } } } diff --git a/Samples/Sponza/Source/CViewInit.cpp b/Samples/Sponza/Source/CViewInit.cpp index 1c91cd469..b6f5fc036 100644 --- a/Samples/Sponza/Source/CViewInit.cpp +++ b/Samples/Sponza/Source/CViewInit.cpp @@ -163,7 +163,7 @@ void CViewInit::initProbes() for (int i = 0; i < 7; i++) { - float x = i * 5.6f - 6.0f * 5.6f; + float x = i * 5.6f - 3.0f * 5.6f; // row 0 probesPosition.push_back(core::vector3df(x, 2.0f, -0.4f));